package ir

import (
	"strconv"

	"gitee.com/u-language/u-language/ucom/enum"
	"gitee.com/u-language/u-language/ucom/internal/utils"
)

// AmexprOptimize 算术表达式优化
//
// 实现下列优化
//   - 如果结果为立即数临时变量（也就是编译期可求值），直接使用该立即数，不等到运行时计算
func (t *ToState) AmexprOptimize(ret *[]IrNode) (ArgObj string, ArgTyp ArgEnum) {
	switch resultret := (*ret)[len(*ret)-1]; resultret.ResultTyp {
	case TmpIntVar:
		ArgTyp = ImmInt
		ArgObj = t.HavaTmpVar(resultret.ResultObj)
		(*ret) = (*ret)[:len(*ret)-1]
	case TmpFloatVar:
		ArgTyp = ImmFloat
		ArgObj = t.HavaTmpVar(resultret.ResultObj)
		(*ret) = (*ret)[:len(*ret)-1]
	}
	if len(*ret) == 0 {
		return
	}
	return (*ret)[len(*ret)-1].ResultObj, (*ret)[len(*ret)-1].ResultTyp
}

// AmexprOptimizeOrImm 算术表达式可能立即数优化
//
// 如果算术表达式的操作数都是立即数（也就是编译期可求值），直接计算该立即数，不等到运行时计算
func (t *ToState) AmexprOptimizeOrImm(ptrir *IrNode, typ int8, op enum.OPSymbol) bool {
	switch typ {
	case intTyp:
		if ptrir.Arg1Typ.IsImm() && ptrir.Arg2Typ.IsImm() { //都是int型立即数，语义检查会保证类型一致
			src1, err := strconv.Atoi(ptrir.Arg1Obj)
			utils.MustErr(err)
			src2, err := strconv.Atoi(ptrir.Arg2Obj)
			utils.MustErr(err)
			result := intop(op, src1, src2)
			tmp := t.auto.Get()
			t.table[tmp] = strconv.Itoa(result)
			ptrir.ResultObj = tmp
			ptrir.ResultTyp = TmpIntVar
			return true
		}
	case floatTyp:
		if ptrir.Arg1Typ.IsImm() && ptrir.Arg2Typ.IsImm() { //都是float型立即数，语义检查会保证类型一致
			src1, err := strconv.ParseFloat(ptrir.Arg1Obj, 64)
			utils.MustErr(err)
			src2, err := strconv.ParseFloat(ptrir.Arg2Obj, 64)
			utils.MustErr(err)
			result := floatop(op, src1, src2)
			tmp := t.auto.Get()
			t.table[tmp] = strconv.FormatFloat(result, 'f', -1, 64)
			ptrir.ResultObj = tmp
			ptrir.ResultTyp = TmpFloatVar
			return true
		}
	}
	return false
}

func intop(op enum.OPSymbol, src1, src2 int) int {
	switch op {
	case enum.ADDOP:
		return src1 + src2
	case enum.SUBOP:
		return src1 - src2
	}
	return 0
}

func floatop(op enum.OPSymbol, src1, src2 float64) float64 {
	switch op {
	case enum.ADDOP:
		return src1 + src2
	case enum.SUBOP:
		return src1 - src2
	}
	return 0
}

// ASSIGNOptimize 赋值转换的中间代码优化
//
// 实现下列优化
//   - 如果赋值源操作数类型为临时变量，且这个临时变量为上一个的结果，将上一个的结果改写为赋值目的操作数
//   - 如果其中任意两条中间代码形如(a op1 b) op2 c,且b和c为立即数，同时op1和op2优先级相同，改写为a op1 (b op2 c)，并编译期求值
func (t *ToState) ASSIGNOptimize(ptrir *IrNode, ret *[]IrNode) {
	if ptrir.Arg1Typ == Tmp {
		if resultret := &((*ret)[len(*ret)-2]); resultret.ResultObj == ptrir.Arg1Obj {
			resultret.ResultObj = ptrir.ResultObj
			resultret.ResultTyp = ptrir.ResultTyp
			(*ret) = (*ret)[:len(*ret)-1]
		}
	}
	if len(*ret) < 2 {
		return
	}
	for i := len(*ret); i >= 2; i-- {
		if src1node, src2node := &((*ret)[i-2]), &((*ret)[i-1]); immTypeEqual(src1node.Arg2Typ, src2node.Arg2Typ) {
			switch src1node.Arg2Typ {
			case ImmInt:
				src1, err := strconv.Atoi(src1node.Arg2Obj)
				utils.MustErr(err)
				src2, err := strconv.Atoi(src2node.Arg2Obj)
				utils.MustErr(err)
				switch src2node.Op {
				case ADDOP:
					src1node.Arg2Obj = strconv.Itoa(intop(enum.ADDOP, src1, src2))
					src1node.ResultObj, src1node.ResultTyp = src2node.ResultObj, src2node.ResultTyp
					(*ret) = append((*ret)[:i-1], (*ret)[i:]...)
				case SUBOP:
					src1node.Arg2Obj = strconv.Itoa(intop(enum.SUBOP, src1, src2))
					src1node.ResultObj, src1node.ResultTyp = src2node.ResultObj, src2node.ResultTyp
					(*ret) = append((*ret)[:i-1], (*ret)[i:]...)
				}
			case ImmFloat:
				src1, err := strconv.ParseFloat(src1node.Arg2Obj, 64)
				utils.MustErr(err)
				src2, err := strconv.ParseFloat(src2node.Arg2Obj, 64)
				utils.MustErr(err)
				switch src2node.Op {
				case ADDOP:
					src1node.Arg2Obj = strconv.FormatFloat(floatop(enum.ADDOP, src1, src2), 'f', -1, 64)
					src1node.ResultObj, src1node.ResultTyp = src2node.ResultObj, src2node.ResultTyp
					(*ret) = append((*ret)[:i-1], (*ret)[i:]...)
				case SUBOP:
					src1node.Arg2Obj = strconv.FormatFloat(floatop(enum.SUBOP, src1, src2), 'f', -1, 64)
					src1node.ResultObj, src1node.ResultTyp = src2node.ResultObj, src2node.ResultTyp
					(*ret) = append((*ret)[:i-1], (*ret)[i:]...)
				}
			}
		}
	}

}

func immTypeEqual(src1, src2 ArgEnum) bool {
	if src1.IsImm() && src2.IsImm() && src1 == src2 {
		return true
	}
	return false
}
